package com.kingyea.mobilepolice.apimanage.utils.ws;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Function;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.ibm.wsdl.xml.WSDLReaderImpl;
import com.kingyea.mobilepolice.apimanage.utils.StateCode;
import com.kingyea.mobilepolice.system.exception.BusinessException;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import org.apache.cxf.service.model.BindingInfo;
import org.apache.cxf.service.model.BindingMessageInfo;
import org.apache.cxf.service.model.BindingOperationInfo;
import org.apache.cxf.service.model.MessagePartInfo;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.wsdl.Definition;
import javax.wsdl.Port;
import javax.wsdl.Service;
import javax.wsdl.xml.WSDLReader;
import javax.xml.namespace.QName;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * web service 工具类
 *
 * @author Mr.Lin
 * @version v1.0.0
 * @description
 * @date Created in 2018/2/9 18:14
 */

public class WSUtils {

    /**
     * @param wsdlUrl     接口描述文件地址
     * @param invokMethod 请求方法名
     * @param queryParams 请求参数
     * @return java.lang.Object[]
     * @author Mr.Lin
     * @date 2018/2/9 18:20
     */
    public static Object[] query(String wsdlUrl, String invokMethod, Object[] queryParams) {
        Object[] result = null;

        final JaxWsDynamicClientFactory clientFactory = JaxWsDynamicClientFactory.newInstance();
        final Client client = clientFactory.createClient(wsdlUrl);
        // 获取op
        final Endpoint endpoint = client.getEndpoint();
        final BindingInfo info = endpoint.getEndpointInfo().getBinding();
        final Collection<BindingOperationInfo> operations = info.getOperations();
        QName op = null;
        for (final BindingOperationInfo bindingOperationInfo : operations) {
            final QName opQName = bindingOperationInfo.getOperationInfo().getName();
            if (opQName.getLocalPart().equals(invokMethod)) {
                op = opQName;
                break;
            }
        }
        try {
            result = client.invoke(op, queryParams);
            System.out.println(result[0]);
            System.out.println(JSON.toJSONString(result));
        } catch (final Exception e) {
            e.printStackTrace();
            throw new BusinessException(StateCode.SYSTEM_ERROR, "接口异常,找不到对应接口! ");
        }
        return result;
    }


    private static void queryTest()throws Exception{

        query("http://localhost:8080/services/Hello?wsdl", "sayHello", new Object[]{"tom", 12});
        query("http://localhost:8080/services/Hello?wsdl", "saySelfInfo", new Object[]{"tom", 12});
        final List<Object> attachments = new ArrayList<>();
        final Object attachmentWrapper = Thread.currentThread().getContextClassLoader()
                .loadClass("com.kingyea.mobilepolice.apimanage.utils.ws.bean.AttachmentWrapper").newInstance();
        final Method m1 = attachmentWrapper.getClass().getMethod("setFileName", String.class);
        final Method m2 = attachmentWrapper.getClass().getMethod("setFileExtension", String.class);
        final Method m3 = attachmentWrapper.getClass().getMethod("setFile", DataHandler.class);
        m1.invoke(attachmentWrapper, "test");
        m2.invoke(attachmentWrapper, "zip");
        m3.invoke(attachmentWrapper, new DataHandler(new FileDataSource(new File("D:/A/test.zip"))));
        attachments.add((attachmentWrapper));
        query("http://localhost:8080/services/Hello?wsdl", "upload", new Object[]{"message", attachments});
        // TODO: 2018.03.21 map 类型不支持 bug
//        query("http://localhost:8080/services/Hello?wsdl","talkNonsense",new Object[]{new HashMap<String,Object>(){{put("name","tom");}}});

    }


    public static void main(final String[] args) throws Exception {

//        queryTest();

        Long startTime = System.currentTimeMillis();
        Object result = invoke("http://localhost:8080/services/Hello?wsdl","speak", "tom");
        System.out.println(result);
        System.out.println("耗时:"+(System.currentTimeMillis()-startTime));

        Long startTime2 = System.currentTimeMillis();
        Object result2 = invoke("http://localhost:8080/services/Hello?wsdl","say", "{\"arg0\":\"tom\",\"arg1\":10}");
        System.out.println(result2);
        System.out.println("耗时:"+(System.currentTimeMillis()-startTime2));

    }



    /**
     * 动态代理工程类
     */
    static final JaxWsDynamicClientFactory wsClientFactory = JaxWsDynamicClientFactory.newInstance();
    public final static Cache<String, Client> clientCache = CacheBuilder.newBuilder()
            .expireAfterWrite(30, TimeUnit.MINUTES).maximumSize(200).build();

    /**
     * 执行soap服务调用
     *
     * @param serviceUrl
     * @param serviceName  被调用的方法名称
     * @param data      单个参数的时候直接写,多个参数时 原始报文用JSON格式 {"arg0":"121212","arg1","3434343"}
     * @return
     * @throws Exception
     */
    public static String invoke(final String serviceUrl, final String serviceName, final String data) throws Exception {
        System.out.println("serviceUrl==" + serviceUrl);
        System.out.println("serviceName==" + serviceName);
        System.out.println("data==" + data);
        System.out.println("clientCache==" + clientCache);
        final Client client = clientCache.get(serviceUrl, new Callable<Client>() {

            @SuppressWarnings("unchecked")
            @Override
            public Client call() throws Exception {
                final WSDLReader reader = new WSDLReaderImpl();
                reader.setFeature("javax.wsdl.verbose", false);
                final Definition definition = reader.readWSDL(serviceUrl);
                final String namespace = definition.getTargetNamespace();
                final Map<QName, Service> serviceMap = definition.getAllServices();
                final Set<QName> serviceKeySet = serviceMap.keySet();
                QName serviceQName = null;
                for (final QName qName : serviceKeySet) {
                    serviceQName = qName;
                    if (serviceQName != null) {
                        break;
                    }
                }
                final Service service = serviceMap.get(serviceQName);
                final Map<String, Port> portMap = service.getPorts();
                final Set<String> portKeySet = portMap.keySet();
                QName portQName = null;
                for (final String string : portKeySet) {
                    portQName = new QName(namespace, string);
                    if (portQName != null) {
                        break;
                    }
                }
                // 创建客户端
                final Client client = wsClientFactory.createClient(serviceUrl,
                        serviceQName, portQName);
                // 设置超时单位为毫秒 默认是30000毫秒,即30秒。
                final HTTPConduit http = (HTTPConduit) client.getConduit();
                final HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
                httpClientPolicy.setConnectionTimeout(10000);  // 连接超时 默认是60000毫秒,即60秒.
                httpClientPolicy.setAllowChunking(false);    // 取消块编码
                httpClientPolicy.setReceiveTimeout(120000);     // 响应超时
                http.setClient(httpClientPolicy);
                return client;
            }
        });

        // 获取op
        final Endpoint endpoint = client.getEndpoint();
        endpoint.getEndpointInfo().setAddress(serviceUrl);
        final BindingInfo info = endpoint.getEndpointInfo().getBinding();
        final Collection<BindingOperationInfo> operations = info.getOperations();
        QName op = null;
        BindingMessageInfo bmi = null;
        for (final BindingOperationInfo bindingOperationInfo : operations) {
            final QName opQName = bindingOperationInfo.getOperationInfo().getName();
            if (opQName.getLocalPart().equals(serviceName)) {
                op = opQName;
                if (!bindingOperationInfo.isUnwrapped()) {
                    // OrderProcess uses document literal wrapped style.
                    bmi = bindingOperationInfo.getWrappedOperation().getInput();
                } else {
                    bmi = bindingOperationInfo.getUnwrappedOperation().getInput();
                }
                break;
            }
        }

        if (op == null) {
            System.out.println(String.format("服务[%s]没有找到该方法%s", serviceUrl, serviceName));
            return null;
        }

        Object[] invokeParams = null;
        final List<MessagePartInfo> messageParts = bmi.getMessageParts(); // 这个可以获取输入的参数信息列表，是排序的，TODO 后面可用来校验输入参数
        // http://reymont.iteye.com/blog/1511176
        // 这个对web服务调用开发有帮助，有模型的介绍
        if (messageParts == null) {
            System.out.println(String.format("服务[%s]的方法%s找不到参数信息", serviceUrl, serviceName));
            return null;
        }
        final int size = messageParts.size();
        if (size == 0) {
            invokeParams = new Object[] {};
        } else if (size == 1) {
            invokeParams = new Object[] {
                    data
            };
        } else { // 该方法接收多个参数 多个参数时 原始报文用JSON格式 {"arg0":"121212","arg1","3434343"}
            try {
                final JSONObject json = JSON.parseObject(data);
                final List<Object> paramList = Lists.transform(messageParts, new Function<MessagePartInfo, Object>() {

                    @Override
                    public Object apply(final MessagePartInfo input) {
                        final String paramName = input.getElementQName().getLocalPart();
                        final Class<?> clazz = input.getTypeClass();// TODO 后续还得考虑支持包装类
                        if (clazz.isArray()) { // 如果是数组 ，
                            final JSONArray jsonArray = json.getJSONArray(paramName);
                            if (jsonArray != null) {

                                final int jaSize = jsonArray.size();
                                final String[] objects = new String[jaSize];
                                for (int i = 0; i < jaSize; i++) {
                                    final Object object = jsonArray.get(i);
                                    if (object instanceof String) {
                                        objects[i] = (String) object;
                                    } else {
                                        objects[i] = JSON.toJSONString(object);
                                    }
                                }
                                return objects;
                            } else {
                                return new String[] {};
                            }
                        } else {
                            final Object object = json.get(paramName);
                            if (object == null) {
                                return null;
                            }
                            if (object instanceof String) {
                                return object;
                            } else {
                                return JSON.toJSONString(object);
                            }
                        }
                    }
                });
                invokeParams = paramList.toArray();
            } catch (final Exception e) {
                final JSONObject jo = new JSONObject();
                for (int i = 0; i < messageParts.size(); i++) {
                    final MessagePartInfo messagePartInfo = messageParts.get(i);
                    final String elName = messagePartInfo.getElementQName().getLocalPart();
                    final String typeName = messagePartInfo.getTypeQName().getLocalPart();
                    jo.put(elName, typeName);
                }
                throw new IllegalArgumentException(
                        String.format("错误的参数格式，无法按方法[%s]的参数要求[%s]进行转换", serviceName, jo.toJSONString()), e);
            }
        }

        final Object[] object = client.invoke(op, invokeParams);
        if (object.length != 0) {
            final Object result = object[0];
            if (result == null) { // 由于命名空间不一致的问题，导致结果为null
                return null;
            }
            if (result instanceof String) {
                return (String) result;
            }
            final Class<?> clazz = result.getClass();
            final String name = clazz.getName();
            String reply = null;
            if (name.endsWith("Response")) { // cxf 异步调用会有这种情况返回值的类为方法名+Response 使用用ASM动态字节码
                final Field field = clazz.getDeclaredField("_return");
                field.setAccessible(true);
                final Object _return = field.get(object);
                reply = String.valueOf(_return);
            } else if (name.endsWith("ArrayOfString")) { // .net 的web服务会出现
                final Field field = clazz.getDeclaredField("string");
                field.setAccessible(true);
                final Object string = field.get(object);
                reply = String.valueOf(string);
            } else {
                reply = String.valueOf(result);
            }

            return reply;
        } else {
            return null;
        }
    }



}
